﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;

using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

#if FEATURE_PROCESS

[assembly: PythonModule("signal", typeof(IronPython.Modules.PythonSignal))]
namespace IronPython.Modules {
    public static partial class PythonSignal {
        public const string __doc__ = """
        This module provides mechanisms to use signal handlers in Python.

        Functions:

        signal() -- set the action for a given signal
        getsignal() -- get the signal action for a given signal
        default_int_handler() -- default SIGINT handler

        signal constants:
        SIG_DFL -- used to refer to the system default handler
        SIG_IGN -- used to ignore the signal
        NSIG -- number of defined signals
        SIGINT, SIGTERM, etc. -- signal numbers

        *** IMPORTANT NOTICE ***
        A signal handler function is called with two arguments:
        the first is the signal number, the second is the interrupted stack frame.
        """;


        [SpecialName]
        public static void PerformModuleReload(PythonContext/*!*/ context, PythonDictionary/*!*/ dict) {
            context.SetModuleState(_PythonSignalStateKey, MakeSignalState(context));
        }


        private static PythonSignalState MakeSignalState(PythonContext context) {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                return MakeNtSignalState(context);
            } else  if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
                return MakePosixSignalState(context);
            } else {
                return MakeSimpleSignalState(context);
            }
        }


        [SupportedOSPlatform("windows")]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static PythonSignalState MakeNtSignalState(PythonContext context) {
            Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
            return new NtSignalState(context);
        }


        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static PythonSignalState MakePosixSignalState(PythonContext context) {
            #if NET
                return new PosixSignalState(context);
            #else
                // PosixSignalState depends on PosixSignalRegistration, which is not available in Mono or .NET Standard 2.0
                return new SimpleSignalState(context);
            #endif
        }


        [MethodImpl(MethodImplOptions.NoInlining)]
        private static PythonSignalState MakeSimpleSignalState(PythonContext context) {
            return new SimpleSignalState(context);
        }


        #region Generated Signal Codes

        // *** BEGIN GENERATED CODE ***
        // generated by function: generate_signal_codes from: generate_os_codes.py


        public static int SIGABRT => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 22 : 6;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGALRM => 14;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        [SupportedOSPlatform("windows")]
        public static int SIGBREAK => 21;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGBUS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 10 : 7;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGCHLD => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 20 : 17;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGCLD => 17;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGCONT => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 19 : 18;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.Unix)]
        [SupportedOSPlatform("macos")]
        public static int SIGEMT => 7;

        public static int SIGFPE => 8;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGHUP => 1;

        public static int SIGILL => 4;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.Unix)]
        [SupportedOSPlatform("macos")]
        public static int SIGINFO => 29;

        public static int SIGINT => 2;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGIO => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 23 : 29;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGIOT => 6;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGKILL => 9;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGPIPE => 13;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGPOLL => 29;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGPROF => 27;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGPWR => 30;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGQUIT => 3;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGRTMAX => 64;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGRTMIN => 34;

        public static int SIGSEGV => 11;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows, PlatformID.MacOSX)]
        [SupportedOSPlatform("linux")]
        public static int SIGSTKFLT => 16;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGSTOP => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 17 : 19;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGSYS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 12 : 31;

        public static int SIGTERM => 15;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGTRAP => 5;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGTSTP => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 18 : 20;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGTTIN => 21;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGTTOU => 22;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGURG => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 16 : 23;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGUSR1 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 30 : 10;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGUSR2 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 31 : 12;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGVTALRM => 26;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGWINCH => 28;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGXCPU => 24;

        [PythonHidden(PlatformsAttribute.PlatformFamily.Windows)]
        [SupportedOSPlatform("linux")]
        [SupportedOSPlatform("macos")]
        public static int SIGXFSZ => 25;

        // *** END GENERATED CODE ***

        #endregion

        public static int NSIG => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 23 : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 32 : 65;

        public const int SIG_DFL = 0;
        public const int SIG_IGN = 1;


        // Windows signals
        [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        public const int CTRL_C_EVENT = 0;
        [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        public const int CTRL_BREAK_EVENT = 1;
        [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        public const int CTRL_CLOSE_EVENT = 2;
        [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        public const int CTRL_LOGOFF_EVENT = 5;
        [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
        public const int CTRL_SHUTDOWN_EVENT = 6;


        public static BuiltinFunction default_int_handler = BuiltinFunction.MakeFunction("default_int_handler",
                    ArrayUtils.ConvertAll(typeof(PythonSignal).GetMember("default_int_handlerImpl"), (x) => (MethodBase)x),
                    typeof(PythonSignal)
                );

        // This must be kept public, but hidden from Python for the __doc__ member to show up on default_int_handler
        [PythonHidden]
        [Documentation("""
        default_int_handler(...)

        The default handler for SIGINT installed by Python.
        It raises KeyboardInterrupt.
        """)]
        public static object default_int_handlerImpl(int signalnum, TraceBackFrame? frame) {
            throw new KeyboardInterruptException("");
        }


        [Documentation("""
        getsignal(signalnum) -> action

        Return the current action for the given signal.

        The return value can be:
          SIG_IGN -- if the signal is being ignored
          SIG_DFL -- if the default action for the signal is in effect
          None    -- if an unknown handler is in effect
          anything else -- the callable Python object used as a handler
        """)]
        public static object? getsignal(CodeContext/*!*/ context, int signalnum) {
            if (signalnum <= 0 || signalnum >= NSIG) throw PythonOps.ValueError("signal number out of range");

            var state = GetPythonSignalState(context);
            lock (state.SyncRoot) {
                if (state.TryGetPyHandler(signalnum, out object? value)) {
                    return value;
                } else {
                    return null;
                }
            }
        }


        [Documentation("""
        signal(signalnum, action) -> action

        Set the action for the given signal.
        
        The action can be SIG_DFL, SIG_IGN, or a callable Python object.
        The previous action is returned.  See getsignal() for possible return values.

        *** IMPORTANT NOTICE ***
        A signal handler function is called with two arguments:
        the first is the signal number, the second is the interrupted stack frame.
        """)]
        public static object? signal(CodeContext/*!*/ context, int signalnum, object? action) {
            // Negative scenarios - signalnum
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                if (Array.IndexOf(_PySupportedSignals_Windows, signalnum) == -1) throw PythonOps.ValueError("invalid signal value");
            } else {
                if (signalnum <= 0 || signalnum >= NSIG) throw PythonOps.ValueError("signal number out of range");
            }
            // Negative scenarios - action
            if (action == null) {
                throw SignalHandlerError();
            } else if (action.GetType() == typeof(int)) {
                int tempAction = (int)action;
                if (tempAction != SIG_DFL && tempAction != SIG_IGN) {
                    throw SignalHandlerError();
                }
            } else if (action == default_int_handler) {
                // no-op
            } else {
                // Must match the signature of PySignalHandler
                PythonFunction? result = action as PythonFunction;
                if (result == null) {
                    // It could still be something like a type that implements __call__
                    if (! PythonOps.IsCallable(context, action)) {
                        throw SignalHandlerError();
                    }
                }
            }

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
                // These signals cannot be handled
                if (signalnum == SIGKILL || signalnum == SIGSTOP) throw PythonNT.GetOsError(PythonErrno.EINVAL);
            }
            object? last_handler = null;
            var state = GetPythonSignalState(context);
            lock (state.SyncRoot) {
                // CPython returns the previous handler for the signal
                if (!state.TryGetPyHandler(signalnum, out last_handler) || last_handler is null) {
                    // null marks signals that cannot be handled or are unsupported
                    throw PythonNT.GetOsError(PythonErrno.EINVAL);
                }

                // Set the new action
                state.SetPyHandler(signalnum, action);
            }

            return last_handler;

            static Exception SignalHandlerError() => PythonOps.TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object");
        }


        [Documentation("""
        NOT YET IMPLEMENTED

        set_wakeup_fd(fd) -> fd

        Sets the fd to be written to (with '\0') when a signal
        comes in.  A library can use this to wakeup select or poll.
        The previous fd is returned.

        The fd must be non-blocking.
        """)]
        public static void set_wakeup_fd(CodeContext/*!*/ context, uint fd) {
            throw new NotImplementedException(); //TODO
        }


        private static readonly object _PythonSignalStateKey = new();


        private static PythonSignalState GetPythonSignalState(CodeContext/*!*/ context) {
            return (PythonSignalState)context.LanguageContext.GetModuleState(_PythonSignalStateKey);
        }


        private static void SetPythonSignalState(CodeContext/*!*/ context, PythonSignalState pss) {
            context.LanguageContext.SetModuleState(_PythonSignalStateKey, pss);
        }

        /// <summary>
        /// This class is used to store the installed signal handlers.
        /// </summary>
        private class PythonSignalState : IDisposable {
            // this provides us with access to the Main thread's stack
            public readonly PythonContext SignalPythonContext;

            public object SyncRoot => PySignalToPyHandler;

            /// <summary>
            /// Map out signal identifiers to their actual handlers.
            /// </summary>
            /// <remarks>
            /// The objects in this array are either:
            /// 1. Int32(SIG_DFL) - let the OS handle the signal in the default way;
            /// 2. Int32(SIG_IGN) - ignore the signal;
            /// 3. a callable Python object - the handler for the signal;
            /// 4. null - the signal (or handling thereof) is not supported on this platform.
            /// </remarks>
            protected readonly object?[] PySignalToPyHandler;

            protected readonly object sig_dfl;
            protected readonly object sig_ign;

            public PythonSignalState(PythonContext pc) {
                SignalPythonContext = pc;
                PySignalToPyHandler = new object[NSIG];

                sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL);
                sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN);

                int[] sigs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? _PySupportedSignals_Windows
                    : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? _PySupportedSignals_MacOS
                    : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? _PySupportedSignals_Linux
                    : throw new NotSupportedException("Unsupported platform for signal module");

                // Setting all defined signals to SIG_DFL
                foreach (int sig in sigs) {
                    PySignalToPyHandler[sig] = sig_dfl;
                }
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
                    for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++) {
                        PySignalToPyHandler[sig] = sig_dfl;
                    }
                }

                // Setting exceptions to the rule
                PySignalToPyHandler[SIGINT] = default_int_handler;
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
                    PySignalToPyHandler[SIGPIPE] = sig_ign;
                    PySignalToPyHandler[SIGXFSZ] = sig_ign;
                }
                if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
                    PySignalToPyHandler[SIGKILL] = null;
                    PySignalToPyHandler[SIGSTOP] = null;
                }
            }


            public virtual bool TryGetPyHandler(int signalnum, out object? value)
                => (value = PySignalToPyHandler[signalnum]) != null;


            public virtual void SetPyHandler(int signalnum, object value)
                => PySignalToPyHandler[signalnum] = value;


            /// <summary>
            /// Call the Python handler callable passing the given signal number as argument to the callable.
            /// </summary>
            protected void CallPythonHandler(int signum, object? handler) {
                if (handler is null) return;

                try {
                    if (handler == default_int_handler) {
                        // We're dealing with the default_int_handlerImpl which we
                        // know doesn't care about the frame parameter
                        default_int_handlerImpl(signum, null);
                    } else {
                        // We're dealing with a callable matching PySignalHandler's signature
                            PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(handler,
                                                                                                typeof(PySignalHandler));

                            if (SignalPythonContext.PythonOptions.Frames) {
                                temp.Invoke(signum, SysModule._getframeImpl(null,
                                                                            0,
                                                                            SignalPythonContext._mainThreadFunctionStack));
                            } else {
                                temp.Invoke(signum, null);
                            }
                    }
                } catch (Exception ex) {
                    SignalPythonContext.DomainManager.SharedIO.ErrorWriter.WriteLine(SignalPythonContext.FormatException(ex));
                }
            }

            public void Dispose() {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            ~PythonSignalState() {
                Dispose(false);
            }

            protected virtual void Dispose(bool disposing) {
                if (!_disposed) {
                    if (disposing) {
                        Array.Clear(PySignalToPyHandler, 0, PySignalToPyHandler.Length);
                    }
                    _disposed = true;
                }
            }
            private bool _disposed = false;
        }


        #region Generated Supported Signals

        // *** BEGIN GENERATED CODE ***
        // generated by function: generate_supported_signals from: generate_os_codes.py

        [SupportedOSPlatform("linux")]
        private static readonly int[] _PySupportedSignals_Linux = [
            SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIO, SIGIOT, SIGKILL, SIGPIPE, SIGPOLL, SIGPROF, SIGPWR, SIGQUIT, SIGRTMAX, SIGRTMIN, SIGSEGV, SIGSTKFLT, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ
        ];

        [SupportedOSPlatform("macos")]
        private static readonly int[] _PySupportedSignals_MacOS = [
            SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGEMT, SIGFPE, SIGHUP, SIGILL, SIGINFO, SIGINT, SIGIO, SIGIOT, SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ
        ];

        [SupportedOSPlatform("windows")]
        private static readonly int[] _PySupportedSignals_Windows = [
            SIGABRT, SIGBREAK, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM
        ];


        // *** END GENERATED CODE ***

        #endregion

        // Signature of Python functions that signal.signal(...) expects to be given
        private delegate object PySignalHandler(int signalnum, TraceBackFrame? frame);
    }
}

#endif
